package cn.zyx.config.log;

import cn.zyx.common.utils.DateUtils;
import cn.zyx.common.utils.sys.log.LogUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.UUID;

/**
 * 日志拦截器 从低到高
 * TRACE - 最详细的日志，用于调试非常细粒度的信息（如方法内部变量值）
 * DEBUG - 调试信息，用于开发环境排查问题
 * INFO - 关键业务信息（如服务启动、重要操作记录）
 * WARN - 警告信息（不影响程序运行，但可能存在潜在问题）
 * ERROR - 错误信息（影响部分功能，但程序仍可运行）
 */
    public class LogInterceptor implements HandlerInterceptor {

    /**
     * 日志对象,SLF4J提供的一个方法，用于获取日志对象。
     */
    protected Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 本地线程变量，将数据缓存在线程内部，线程之间互补影响
     * ThreadLocalMap key value
     * key存 ThreadLocal 本身 value 存对应的值
     * 因此 set get 都是获取的当前线程对象
     * （在线程池中使用会有内存泄漏的问题，
     * 因为线程池中的核心线程不会被回收，线程对象通过强引用指向ThreadLocalMap（entry key value对象），
     * 因此，entry对象也不被回收，如果value特别大的话，就会有内存泄漏风险，可以在使用完手动调用remove方法解决）
     */
    private static final ThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<>("ThreadLocal StartTime");

    /**
     * 调用时间：Controller方法处理之前
     * 若返回false，则中断执行，注意：不会进入postHandle,afterCompletion
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 判断日志级别
        if (logger.isDebugEnabled()) {
            // 开始时间
            long beginTime = System.currentTimeMillis();
            // 线程绑定变量（该数据只有当前请求的线程可见）
            startTimeThreadLocal.set(beginTime);
            logger.debug("开始计时: {}  URI: {}",
                    new SimpleDateFormat("hh:mm:ss.SSS").format(beginTime),
                    request.getRequestURI());
        }
        return true;
    }

    /**
     * 调用前提：preHandle返回true
     * 调用时间：Controller方法处理完之后，DispatcherServlet进行视图的渲染之前，也就是说在这个方法中你可以对ModelAndView进行操作
     * 备注：postHandle虽然post打头，但post、get方法都能处理
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) {
        if (modelAndView != null) {
            logger.info("ViewName: " + modelAndView.getViewName());
        }
    }

    /**
     * afterCompletion
     * 调用前提：preHandle返回true
     * 调用时间：DispatcherServlet进行视图的渲染之后
     * 清理资源
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {

        // 保存日志
        LogUtils.saveLog(request, handler, ex, null, null);

        // 打印JVM信息。
        if (logger.isDebugEnabled()) {
            // 得到线程绑定的局部变量（开始时间）
            long beginTime = startTimeThreadLocal.get();
            // 结束时间
            long endTime = System.currentTimeMillis();
            logger.debug("计时结束：{}  耗时：{}  URI: {}  最大内存: {}m  已分配内存: {}m  已分配内存中的剩余空间: {}m  最大可用内存: {}m",
                    new SimpleDateFormat("hh:mm:ss.SSS").format(endTime),
                    DateUtils.formatDateTime(endTime - beginTime),
                    request.getRequestURI(),
                    Runtime.getRuntime().maxMemory() / 1024 / 1024,
                    Runtime.getRuntime().totalMemory() / 1024 / 1024,
                    Runtime.getRuntime().freeMemory() / 1024 / 1024,
                    (Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory() + Runtime.getRuntime().freeMemory()) / 1024 / 1024);
        }

    }

}
